package it.unibo.refolding.pico;

import it.unibo.refolding.common.CommonThings;

import org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;

import com.google.common.base.Preconditions;

class CurveSpec {
	
	public static final CurveSpec allNaN = new CurveSpec(
		Double.NaN, Double.NaN, Double.NaN, Double.NaN);
	
	public final double scanRateHz, zScanStartM, rampSizeM, surfaceDelayS;
	
	public CurveSpec(double scanRateHz, double zScanStartM, double rampSizeM, double surfaceDelayS) {
		Preconditions.checkArgument(CommonThings.isNaNOrFiniteNonNegative(scanRateHz));
		Preconditions.checkArgument(CommonThings.isNaNOrFiniteNonNegative(rampSizeM));
		Preconditions.checkArgument(CommonThings.isNaNOrFiniteNonNegative(surfaceDelayS));
		this.scanRateHz = scanRateHz;
		this.zScanStartM = zScanStartM;
		this.rampSizeM = rampSizeM;
		this.surfaceDelayS = surfaceDelayS;
	}

	public int differences(CurveSpec other) {
		int diff = 0;
		diff += Double.doubleToLongBits(scanRateHz) != Double.doubleToLongBits(other.scanRateHz) ? 1 : 0;
		diff += Double.doubleToLongBits(zScanStartM) != Double.doubleToLongBits(other.zScanStartM) ? 1 : 0;
		diff += Double.doubleToLongBits(rampSizeM) != Double.doubleToLongBits(other.rampSizeM) ? 1 : 0;
		diff += Double.doubleToLongBits(surfaceDelayS) != Double.doubleToLongBits(other.surfaceDelayS) ? 1 : 0;
		return diff;
	}

	public static CurveSpec extract(PlainPicoData picoData) {
		return new CurveSpec(
			picoData.scanRateHz, picoData.zScanStartMetres(), picoData.rampSizeMetres(), picoData.rampDelayS);
	}

	public boolean close(CurveSpec o, double allowedError, double zScanStartAllowedError) {
		return approxEqual(scanRateHz, o.scanRateHz, allowedError) & approxEqual(zScanStartM, o.zScanStartM, zScanStartAllowedError) & approxEqual(rampSizeM, o.rampSizeM, allowedError) & approxEqual(surfaceDelayS, o.surfaceDelayS, allowedError);
	}

	static boolean approxEqual(double x, double y, double allowedError) {
		if (Double.isNaN(x) | Double.isNaN(y)) {
			return Double.isNaN(x) & Double.isNaN(y);
		}
		if (x != 0 & y != 0) {
			return x * y >= 0 & Math.abs(x - y) / (Math.abs(x + y) / 2) <= allowedError;
		} else {
			return Math.abs(x) <= allowedError & Math.abs(y) <= allowedError;
		}
	}
	
	public boolean equals(Object o) {
		if (o == null || o.getClass() != getClass()) {
			return false;
		}
		CurveSpec other = (CurveSpec) o;
		return differences(other) == 0;
	}
	
	@Override
	public String toString() {
		return ReflectionToStringBuilder.toString(this, ToStringStyle.SHORT_PREFIX_STYLE);
	}
}